package com.sinping.qh.service;

import com.google.common.collect.Lists;
import com.mongodb.BasicDBObject;
import com.sinping.qh.query.base.AggregateParams;
import com.sinping.qh.query.base.AggregateQuery;
import com.sinping.qh.query.base.Operator;
import com.sinping.qh.query.base.QueryParams;
import lombok.AllArgsConstructor;
import net.sf.cglib.beans.BeanMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 查询时, 尽量以sql语句的形式查询去所有数据,避免在类中处理数据
 * 了解Criteria,Aggregation,以及MongoTemplate的用法
 * @author wupeng
 * @create 2018/5/29
 */
@AllArgsConstructor
@Component
public class MongoManager {

    private MongoTemplate template;

    public MongoTemplate getTemplate(){
        return this.template;
    }


    /**
     * 根据参数 分页查询
     * @param params 参数
     * @param pageable 分页信息
     * @param clazz 对象类型
     * @param <T>
     * @return 对象分页集合
     */
    public <T> Page<T> findPage(QueryParams params, Pageable pageable, Class<T> clazz) {
        Query query = new Query(where(params)).with(pageable);
        List<T> list = template.find(query, clazz);
        long total = template.count(query, clazz);
        return new PageImpl<>(list, pageable, total);
    }

    public <T> Page<T> findPageByAgg(AggregateQuery aggregateQuery, QueryParams params,Pageable pageable, Class<T> clazz) {

        Criteria criteria=aggCriteria(aggregateQuery);
//        Criteria criteria.andOperator(where(params));

        Query query = new Query(criteria).with(pageable);
        List<T> list = template.find(query, clazz);
        long total = template.count(query, clazz);
        return new PageImpl<>(list, pageable, total);
    }

    /**
     * 根据参数获取对象
     * @param params 参数
     * @param clazz 对象类型
     * @param <T>
     * @return 对象集合
     */
    public <T> List<T> find(QueryParams params, Class<T> clazz) {
        return find(params, clazz, null);
    }

    /**
     *  根据参数获取对象 (排序)
     * @param params 参数
     * @param clazz 类型
     * @param sort 排序规则
     * @param <T>
     * @return 对象集合
     */
    public <T> List<T> find(QueryParams params, Class<T> clazz, Sort sort) {
        Query query = new Query(where(params));
        if (null != sort) {
            query.with(sort);
        }
        return template.find(query, clazz);
    }

    /**
     * 根据参数 获取 对象数量
     * @param params 参数
     * @param clazz 类型
     * @param <T>
     * @return 查询结果条数
     */
    public <T> long count(QueryParams params, Class<T> clazz) {
        Query query = new Query(where(params));
        return template.count(query, clazz);
    }

    /**
     * 分组统计
     * @param aggregateQuery 查询参数
     * @param sort  排序
     * @param groupParams 分组字段
     * @param aggregateParams 统计字段
     * @param clazz 查询对象
     * @return list <BasicDBObject></>
     */
    public List<BasicDBObject> aggregate(AggregateQuery aggregateQuery, Sort sort, List<String> groupParams, AggregateParams aggregateParams, Class<?> clazz) {
        return aggregate(aggregateQuery, sort, groupParams, aggregateParams, clazz, BasicDBObject.class);
    }



    public <T> List<T> aggregate(AggregateQuery aggregateQuery, Sort sort,
                                      List<String> groupParams, AggregateParams aggregateParams,
                                      Class<?> clazz, Class<T> targetClazz) {
        List<AggregationOperation> operations = Lists.newArrayList();
        Criteria criteria=aggCriteria(aggregateQuery);
        //查询参数
        operations.add(Aggregation.match(criteria));
        if (null != sort) {
            operations.add(Aggregation.sort(sort));
        }
        // 统计参数
        GroupOperation group = Aggregation.group(groupParams.toArray(new String[groupParams.size()]));
        for (String name : aggregateParams.getFieldNames()) {
            switch (aggregateParams.getAggregateOperator(name)) {
                case avg:
                    group = group.avg(name).as(aggregateParams.getAlias(name));
                    break;
                case max:
                    group = group.max(name).as(aggregateParams.getAlias(name));
                    break;
                case min:
                    group = group.min(name).as(aggregateParams.getAlias(name));
                    break;
                case sum:
                    group = group.sum(name).as(aggregateParams.getAlias(name));
                    break;
                case last:
                    group = group.last(name).as(aggregateParams.getAlias(name));
                    break;
                case first:
                    group = group.first(name).as(aggregateParams.getAlias(name));
                    break;
                case count:
                default:
                    group = group.count().as(aggregateParams.getAlias(name));
                    break;
            }
        }
        operations.add(group);
        Aggregation aggregation = Aggregation.newAggregation(operations);
        String table = template.getCollectionName(clazz);
        AggregationResults<T> results = template.aggregate(aggregation, table, targetClazz);
        return results.getMappedResults();
    }

    /**
     * 关联查询
     * @param params 查询条件
     * @param fromClazz 关联类
     * @param localField 集合字段
     * @param foreignField 关联类对应字段
     * @param alias 别名
     * @param clazz 集合类
     * @param documentParams 子文档查询条件
     * @return 关联信息
     */
    public List<BasicDBObject> lookUp(QueryParams params, Class<?> fromClazz, String localField, String foreignField,
                                      String alias, Class<?> clazz, QueryParams documentParams) {
        return lookUp(params, fromClazz, localField, foreignField, alias, clazz, documentParams, BasicDBObject.class);
    }

    /**
     * 关联查询
     * @param params 查询条件
     * @param fromClazz 关联类
     * @param localField 集合字段
     * @param foreignField 关联类对应字段
     * @param alias 别名
     * @param clazz 集合类
     * @param documentParams 子文档查询条件
     * @param target 需要返回的对象
     * @param <T>
     * @return List<对象></>
     */
    public <T> List<T> lookUp(QueryParams params, Class<?> fromClazz, String localField, String foreignField,
                              String alias, Class<?> clazz, QueryParams documentParams, Class<T> target) {
        List<AggregationOperation> operations = Lists.newArrayList();
        //查询参数
        operations.add(Aggregation.match(where(params)));
        String targetCollection = template.getCollectionName(fromClazz);
        LookupOperation operation = LookupOperation.newLookup()
                .from(targetCollection)
                .localField(localField)
                .foreignField(foreignField)
                .as(alias);
        operations.add(operation);
        if (null != documentParams) {
            operations.add(Aggregation.match(documentWhere(documentParams, alias)));
        }
        Aggregation aggregation = Aggregation.newAggregation(operations);
        String table = template.getCollectionName(clazz);
        AggregationResults<T> results = template.aggregate(aggregation, table, target);
        return results.getMappedResults();
    }

    /**
     * 合并多个查询
     * @return
     */
    public Criteria aggCriteria(AggregateQuery aggregateQuery) {
        Criteria mainCriteria = new Criteria();
        if(aggregateQuery.getQueryParams().size()==0) {
            return mainCriteria;
        }
        Criteria[] arrs=new Criteria[aggregateQuery.getQueryParams().size()];
        for (int i=0;i<arrs.length;i++) {
            arrs[i]=(where(aggregateQuery.getQueryParams().get(i)));
        }
        if(arrs.length==1) {
            return arrs[0];
        }
        switch (aggregateQuery.getOperator()) {
            case or:
                mainCriteria.orOperator(arrs);
                break;
            case and:
                mainCriteria.andOperator(arrs);
                break;
        }

       return mainCriteria;
    }

    /**
     * 将参数转换成查询信息
     * @param queryParams 参数
     * @return 查询
     */
    public Criteria where(QueryParams queryParams) {
        Criteria criteria = new Criteria();
        if (null == queryParams) {
            return criteria;
        }
        Map<String, Criteria> criteriaMap = new HashMap<>();
        Map<String, Object> beanMap = transferBeanToMap(queryParams);
        beanMap.forEach((key, value) -> {
            String fieldName = queryParams.getAlias(key);
            Operator operator = queryParams.getOperators(key);
            Criteria mapCriteria = criteriaMap.get(fieldName);
            if (null == mapCriteria) {
                mapCriteria = criteria.and(fieldName);
                criteriaMap.put(fieldName, mapCriteria);
            }
            operatorCriteria(operator, mapCriteria, value);
        });
        return criteria;
    }

    /**
     * 子文档的查询方式
     * @param queryParams 查询条件
     * @param alias 别名
     * @return 查询语句
     */
    private Criteria documentWhere(QueryParams queryParams, String alias) {
        Criteria criteria = new Criteria();
        if (null == queryParams) {
            return criteria;
        }
        String prefix = "";
        if (StringUtils.isNotBlank(alias)) {
            prefix = alias + ".";
        }
        Map<String, Criteria> criteriaMap = new HashMap<>();
        Map<String, Object> beanMap = transferBeanToMap(queryParams);
        String finalPrefix = prefix;
        beanMap.forEach((key, value) -> {
            String fieldName = finalPrefix + queryParams.getAlias(key);
            Operator operator = queryParams.getOperators(key);
            Criteria mapCriteria = criteriaMap.get(fieldName);
            if (null == mapCriteria) {
                mapCriteria = criteria.and(fieldName);
                criteriaMap.put(fieldName, mapCriteria);
            }
            operatorCriteria(operator, mapCriteria, value);
        });
        return criteria;
    }

    /**
     * 根据操作符 给 Criteria 设置值
     * @param operator 操作符
     * @param mapCriteria query语句
     * @param value 值
     */
    private void operatorCriteria(Operator operator, Criteria mapCriteria, Object value) {
        switch (operator) {
            case eq:
                mapCriteria.is(value);
                break;
            case gte:
                mapCriteria.gte(value);
                break;
            case lt:
                mapCriteria.lt(value);
                break;
            case lte:
                mapCriteria.lte(value);
                break;
            case ne:
                mapCriteria.ne(value);
                break;
            case regex:
                mapCriteria.regex((String) value);
                break;
            case in:
                if(value instanceof Collection)
                {
                    mapCriteria.in((Collection)value);
                }else {
                    mapCriteria.in(value);
                }
                break;
            default:
                break;
        }
    }


    /**
     * 将对象 转成 map
     * @param object 对象
     * @return map
     */
    private Map<String, Object> transferBeanToMap(Object object) {
        Map<String, Object> map = new HashMap<String, Object>();
        if (null != object) {
            /*将对象转换成 BeanMap 对象*/
            BeanMap beanMap = BeanMap.create(object);
            beanMap.forEach((key, value) -> {
                if (null != value) {
                    if (value instanceof String) {
                        if (((String) value).length() >= 0) {
                            map.put((String) key, value);
                        }
                    } else {
                        map.put((String) key, value);
                    }
                }
            });
        }
        return map;
    }

}
